home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2010 April / PCWorld0410.iso / pluginy Firefox / 1843 / 1843.xpi / content / firebug / domplate.js < prev    next >
Text File  |  2010-01-15  |  30KB  |  1,083 lines

  1. /* See license.txt for terms of usage */
  2.  
  3. // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  4.  
  5. function DomplateTag(tagName)
  6. {
  7.     this.tagName = tagName;
  8. }
  9.  
  10. function DomplateEmbed()
  11. {
  12. }
  13.  
  14. function DomplateLoop()
  15. {
  16. }
  17.  
  18. // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  19.  
  20. (function() {
  21.  
  22. var womb = null;
  23.  
  24. top.domplate = function()
  25. {
  26.     var lastSubject;
  27.     for (var i = 0; i < arguments.length; ++i)
  28.         lastSubject = lastSubject ? copyObject(lastSubject, arguments[i]) : arguments[i];
  29.  
  30.     for (var name in lastSubject)
  31.     {
  32.         var val = lastSubject[name];
  33.         if (isTag(val))
  34.             val.tag.subject = lastSubject;
  35.     }
  36.  
  37.     return lastSubject;
  38. };
  39.  
  40. domplate.context = function(context, fn)
  41. {
  42.     var lastContext = domplate.lastContext;
  43.     domplate.topContext = context;
  44.     fn.apply(context);
  45.     domplate.topContext = lastContext;
  46. };
  47.  
  48. FBL.TAG = function()
  49. {
  50.     var embed = new DomplateEmbed();
  51.     return embed.merge(arguments);
  52. };
  53.  
  54. FBL.FOR = function()
  55. {
  56.     var loop = new DomplateLoop();
  57.     return loop.merge(arguments);
  58. };
  59.  
  60. DomplateTag.prototype =
  61. {
  62.     /*
  63.      *  Initializer for DOM templates. Called to create new Functions objects like TR, TD, OBJLINK, etc. See defineTag
  64.      *  @param args keyword argments for the template, the {} brace stuff after the tag name, eg TR({...}, TD(...
  65.      *  @param oldTag a nested tag, eg the TD tag in TR({...}, TD(...
  66.      */
  67.     merge: function(args, oldTag)
  68.     {
  69.         if (oldTag)
  70.             this.tagName = oldTag.tagName;
  71.  
  72.         this.context = oldTag ? oldTag.context : null;  // normally null on construction
  73.         this.subject = oldTag ? oldTag.subject : null;
  74.         this.attrs = oldTag ? copyObject(oldTag.attrs) : {};
  75.         this.classes = oldTag ? copyObject(oldTag.classes) : {};
  76.         this.props = oldTag ? copyObject(oldTag.props) : null;
  77.         this.listeners = oldTag ? copyArray(oldTag.listeners) : null;
  78.         this.children = oldTag ? copyArray(oldTag.children) : [];
  79.         this.vars = oldTag ? copyArray(oldTag.vars) : [];
  80.  
  81.         var attrs = args.length ? args[0] : null;
  82.         var hasAttrs = typeof(attrs) == "object" && !isTag(attrs);
  83.  
  84.         // Do not clear children, they can be copied from the oldTag.
  85.         //this.children = [];
  86.  
  87.         if (domplate.topContext)
  88.             this.context = domplate.topContext;
  89.  
  90.         if (args.length)
  91.             parseChildren(args, hasAttrs ? 1 : 0, this.vars, this.children);
  92.  
  93.         if (hasAttrs)
  94.             this.parseAttrs(attrs);
  95.  
  96.         return creator(this, DomplateTag);
  97.     },
  98.  
  99.     parseAttrs: function(args)
  100.     {
  101.         for (var name in args)
  102.         {
  103.             var val = parseValue(args[name]);
  104.             readPartNames(val, this.vars);
  105.  
  106.             if (name.indexOf("on") == 0)
  107.             {
  108.                 var eventName = name.substr(2);
  109.                 if (!this.listeners)
  110.                     this.listeners = [];
  111.                 this.listeners.push(eventName, val);
  112.             }
  113.             else if (name[0] == "_")
  114.             {
  115.                 var propName = name.substr(1);
  116.                 if (!this.props)
  117.                     this.props = {};
  118.                 this.props[propName] = val;
  119.             }
  120.             else if (name[0] == "$")
  121.             {
  122.                 var className = name.substr(1);
  123.                 if (!this.classes)
  124.                     this.classes = {};
  125.                 this.classes[className] = val;
  126.             }
  127.             else
  128.             {
  129.                 if (name == "class" && this.attrs.hasOwnProperty(name) )
  130.                     this.attrs[name] += " " + val;
  131.                 else
  132.                     this.attrs[name] = val;
  133.             }
  134.         }
  135.     },
  136.  
  137.     compile: function()
  138.     {
  139.         if (this.renderMarkup)
  140.             return;
  141.  
  142.         this.compileMarkup();
  143.         this.compileDOM();
  144.     },
  145.  
  146.     compileMarkup: function()
  147.     {
  148.         this.markupArgs = [];
  149.         var topBlock = [], topOuts = [], blocks = [], info = {args: this.markupArgs, argIndex: 0};
  150.  
  151.         this.generateMarkup(topBlock, topOuts, blocks, info);
  152.         this.addCode(topBlock, topOuts, blocks);
  153.  
  154.         var fnBlock = ['(function (__code__, __context__, __in__, __out__'];
  155.         for (var i = 0; i < info.argIndex; ++i)
  156.             fnBlock.push(', s', i);
  157.         fnBlock.push(') {\n');
  158.  
  159.         if (this.subject)
  160.             fnBlock.push('with (this) {\n');
  161.         if (this.context)
  162.             fnBlock.push('with (__context__) {\n');
  163.         fnBlock.push('with (__in__) {\n');
  164.  
  165.         fnBlock.push.apply(fnBlock, blocks);
  166.  
  167.         if (this.subject)
  168.             fnBlock.push('}\n');
  169.         if (this.context)
  170.             fnBlock.push('}\n');
  171.  
  172.         fnBlock.push('}})\n');
  173.  
  174.         function __link__(tag, code, outputs, args)
  175.         {
  176.             if (!tag || !tag.tag)
  177.                 return;
  178.  
  179.             tag.tag.compile();
  180.  
  181.             var tagOutputs = [];
  182.             var markupArgs = [code, tag.tag.context, args, tagOutputs];
  183.             markupArgs.push.apply(markupArgs, tag.tag.markupArgs);
  184.             tag.tag.renderMarkup.apply(tag.tag.subject, markupArgs);
  185.  
  186.             outputs.push(tag);
  187.             outputs.push(tagOutputs);
  188.         }
  189.  
  190.         function __escape__(value)
  191.         {
  192.             function replaceChars(ch)
  193.             {
  194.                 switch (ch)
  195.                 {
  196.                     case "<":
  197.                         return "<";
  198.                     case ">":
  199.                         return ">";
  200.                     case "&":
  201.                         return "&";
  202.                     case "'":
  203.                         return "'";
  204.                     case '"':
  205.                         return """;
  206.                 }
  207.                 return "?";
  208.             };
  209.             return String(value).replace(/[<>&"']/g, replaceChars);
  210.         }
  211.  
  212.         function __loop__(iter, outputs, fn)
  213.         {
  214.             var iterOuts = [];
  215.             outputs.push(iterOuts);
  216.  
  217.             if (iter instanceof Array)
  218.                 iter = new ArrayIterator(iter);
  219.  
  220.             try
  221.             {
  222.                 while (1)
  223.                 {
  224.                     var value = iter.next();
  225.                     var itemOuts = [0,0];
  226.                     iterOuts.push(itemOuts);
  227.                     fn.apply(this, [value, itemOuts]);
  228.                 }
  229.             }
  230.             catch (exc)
  231.             {
  232.                 if (exc != StopIteration)
  233.                     throw exc;
  234.             }
  235.         }
  236.  
  237.         var js = fnBlock.join("");
  238.         this.renderMarkup = eval(js);
  239.     },
  240.  
  241.     getVarNames: function(args)
  242.     {
  243.         if (this.vars)
  244.             args.push.apply(args, this.vars);
  245.  
  246.         for (var i = 0; i < this.children.length; ++i)
  247.         {
  248.             var child = this.children[i];
  249.             if (isTag(child))
  250.                 child.tag.getVarNames(args);
  251.             else if (child instanceof Parts)
  252.             {
  253.                 for (var i = 0; i < child.parts.length; ++i)
  254.                 {
  255.                     if (child.parts[i] instanceof Variable)
  256.                     {
  257.                         var name = child.parts[i].name;
  258.                         var names = name.split(".");
  259.                         args.push(names[0]);
  260.                     }
  261.                 }
  262.             }
  263.         }
  264.     },
  265.  
  266.     generateMarkup: function(topBlock, topOuts, blocks, info)
  267.     {
  268.         topBlock.push(',"<', this.tagName, '"');
  269.  
  270.         for (var name in this.attrs)
  271.         {
  272.             if (name != "class")
  273.             {
  274.                 var val = this.attrs[name];
  275.                 topBlock.push(', " ', name, '=\\""');
  276.                 addParts(val, ',', topBlock, info, true);
  277.                 topBlock.push(', "\\""');
  278.             }
  279.         }
  280.  
  281.         if (this.listeners)
  282.         {
  283.             for (var i = 0; i < this.listeners.length; i += 2)
  284.                 readPartNames(this.listeners[i+1], topOuts);
  285.         }
  286.  
  287.         if (this.props)
  288.         {
  289.             for (var name in this.props)
  290.                 readPartNames(this.props[name], topOuts);
  291.         }
  292.  
  293.         if ( this.attrs.hasOwnProperty("class") || this.classes)
  294.         {
  295.             topBlock.push(', " class=\\""');
  296.             if (this.attrs.hasOwnProperty("class"))
  297.                 addParts(this.attrs["class"], ',', topBlock, info, true);
  298.               topBlock.push(', " "');
  299.             for (var name in this.classes)
  300.             {
  301.                 topBlock.push(', (');
  302.                 addParts(this.classes[name], '', topBlock, info);
  303.                 topBlock.push(' ? "', name, '" + " " : "")');
  304.             }
  305.             topBlock.push(', "\\""');
  306.         }
  307.         topBlock.push(',">"');
  308.  
  309.         this.generateChildMarkup(topBlock, topOuts, blocks, info);
  310.         topBlock.push(',"</', this.tagName, '>"');
  311.     },
  312.  
  313.     generateChildMarkup: function(topBlock, topOuts, blocks, info)
  314.     {
  315.         for (var i = 0; i < this.children.length; ++i)
  316.         {
  317.             var child = this.children[i];
  318.             if (isTag(child))
  319.                 child.tag.generateMarkup(topBlock, topOuts, blocks, info);
  320.             else
  321.                 addParts(child, ',', topBlock, info, true);
  322.         }
  323.     },
  324.  
  325.     addCode: function(topBlock, topOuts, blocks)
  326.     {
  327.         if (topBlock.length)
  328.             blocks.push('__code__.push(""', topBlock.join(""), ');\n');
  329.         if (topOuts.length)
  330.             blocks.push('__out__.push(', topOuts.join(","), ');\n');
  331.         topBlock.splice(0, topBlock.length);
  332.         topOuts.splice(0, topOuts.length);
  333.     },
  334.  
  335.     addLocals: function(blocks)
  336.     {
  337.         var varNames = [];
  338.         this.getVarNames(varNames);
  339.  
  340.         var map = {};
  341.         for (var i = 0; i < varNames.length; ++i)
  342.         {
  343.             var name = varNames[i];
  344.             if ( map.hasOwnProperty(name) )
  345.                 continue;
  346.  
  347.             map[name] = 1;
  348.             var names = name.split(".");
  349.             blocks.push('var ', names[0] + ' = ' + '__in__.' + names[0] + ';\n');
  350.         }
  351.     },
  352.  
  353.     compileDOM: function()
  354.     {
  355.         var path = [];
  356.         var blocks = [];
  357.         this.domArgs = [];
  358.         path.embedIndex = 0;
  359.         path.loopIndex = 0;
  360.         path.staticIndex = 0;
  361.         path.renderIndex = 0;
  362.         var nodeCount = this.generateDOM(path, blocks, this.domArgs);
  363.  
  364.         var fnBlock = ['(function (root, context, o'];
  365.  
  366.         for (var i = 0; i < path.staticIndex; ++i)
  367.             fnBlock.push(', ', 's'+i);
  368.  
  369.         for (var i = 0; i < path.renderIndex; ++i)
  370.             fnBlock.push(', ', 'd'+i);
  371.  
  372.         fnBlock.push(') {\n');
  373.         for (var i = 0; i < path.loopIndex; ++i)
  374.             fnBlock.push('var l', i, ' = 0;\n');
  375.         for (var i = 0; i < path.embedIndex; ++i)
  376.             fnBlock.push('var e', i, ' = 0;\n');
  377.  
  378.         if (this.subject)
  379.             fnBlock.push('with (this) {\n');
  380.         if (this.context)
  381.             fnBlock.push('with (context) {\n');
  382.  
  383.         fnBlock.push(blocks.join(""));
  384.  
  385.         if (this.subject)
  386.             fnBlock.push('}\n');
  387.         if (this.context)
  388.             fnBlock.push('}\n');
  389.  
  390.         fnBlock.push('return ', nodeCount, ';\n');
  391.         fnBlock.push('})\n');
  392.  
  393.         function __bind__(object, fn)
  394.         {
  395.             return function(event) { return fn.apply(object, [event]); }
  396.         }
  397.  
  398.         function __link__(node, tag, args)
  399.         {
  400.             if (!tag || !tag.tag)
  401.                 return;
  402.  
  403.             tag.tag.compile();
  404.  
  405.             var domArgs = [node, tag.tag.context, 0];
  406.             domArgs.push.apply(domArgs, tag.tag.domArgs);
  407.             domArgs.push.apply(domArgs, args);
  408.  
  409.             return tag.tag.renderDOM.apply(tag.tag.subject, domArgs);
  410.         }
  411.  
  412.         var self = this;
  413.         function __loop__(iter, fn)
  414.         {
  415.             var nodeCount = 0;
  416.             for (var i = 0; i < iter.length; ++i)
  417.             {
  418.                 iter[i][0] = i;
  419.                 iter[i][1] = nodeCount;
  420.                 nodeCount += fn.apply(this, iter[i]);
  421.             }
  422.             return nodeCount;
  423.         }
  424.  
  425.         function __path__(parent, offset)
  426.         {
  427.             var root = parent;
  428.  
  429.             for (var i = 2; i < arguments.length; ++i)
  430.             {
  431.                 var index = arguments[i];
  432.                 if (i == 3)
  433.                     index += offset;
  434.  
  435.                 if (index == -1)
  436.                     parent = parent.parentNode;
  437.                 else
  438.                     parent = parent.childNodes[index];
  439.             }
  440.  
  441.             return parent;
  442.         }
  443.         var js = fnBlock.join("");
  444.         // Exceptions on this line are often in the eval
  445.         this.renderDOM = eval(js);
  446.     },
  447.  
  448.     generateDOM: function(path, blocks, args)
  449.     {
  450.         if (this.listeners || this.props)
  451.             this.generateNodePath(path, blocks);
  452.  
  453.         if (this.listeners)
  454.         {
  455.             for (var i = 0; i < this.listeners.length; i += 2)
  456.             {
  457.                 var val = this.listeners[i+1];
  458.                 var arg = generateArg(val, path, args);
  459.                 blocks.push('node.addEventListener("', this.listeners[i], '", __bind__(this, ', arg, '), false);\n');
  460.             }
  461.         }
  462.  
  463.         if (this.props)
  464.         {
  465.             for (var name in this.props)
  466.             {
  467.                 var val = this.props[name];
  468.                 var arg = generateArg(val, path, args);
  469.                 blocks.push('node.', name, ' = ', arg, ';\n');
  470.             }
  471.         }
  472.  
  473.         this.generateChildDOM(path, blocks, args);
  474.         return 1;
  475.     },
  476.  
  477.     generateNodePath: function(path, blocks)
  478.     {
  479.         blocks.push("var node = __path__(root, o");
  480.         for (var i = 0; i < path.length; ++i)
  481.             blocks.push(",", path[i]);
  482.         blocks.push(");\n");
  483.     },
  484.  
  485.     generateChildDOM: function(path, blocks, args)
  486.     {
  487.         path.push(0);
  488.         for (var i = 0; i < this.children.length; ++i)
  489.         {
  490.             var child = this.children[i];
  491.             if (isTag(child))
  492.                 path[path.length-1] += '+' + child.tag.generateDOM(path, blocks, args);
  493.             else
  494.                 path[path.length-1] += '+1';
  495.         }
  496.         path.pop();
  497.     },
  498.  
  499.     /*
  500.      * We are just hiding from javascript.options.strict. For some reasons it's ok if we return undefined here.
  501.      * @return null or undefined or possibly a context.
  502.      */
  503.     getContext: function()
  504.     {
  505.         return this.context;
  506.     }
  507. };
  508.  
  509. // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  510.  
  511. DomplateEmbed.prototype = copyObject(DomplateTag.prototype,
  512. {
  513.     merge: function(args, oldTag)
  514.     {
  515.         this.value = oldTag ? oldTag.value : parseValue(args[0]);
  516.         this.attrs = oldTag ? oldTag.attrs : {};
  517.         this.vars = oldTag ? copyArray(oldTag.vars) : [];
  518.  
  519.         var attrs = args[1];
  520.         for (var name in attrs)
  521.         {
  522.             var val = parseValue(attrs[name]);
  523.             this.attrs[name] = val;
  524.             readPartNames(val, this.vars);
  525.         }
  526.  
  527.         return creator(this, DomplateEmbed);
  528.     },
  529.  
  530.     getVarNames: function(names)
  531.     {
  532.         if (this.value instanceof Parts)
  533.             names.push(this.value.parts[0].name);
  534.  
  535.         if (this.vars)
  536.             names.push.apply(names, this.vars);
  537.     },
  538.  
  539.     generateMarkup: function(topBlock, topOuts, blocks, info)
  540.     {
  541.         this.addCode(topBlock, topOuts, blocks);
  542.  
  543.         blocks.push('__link__(');
  544.         addParts(this.value, '', blocks, info);
  545.         blocks.push(', __code__, __out__, {\n');
  546.  
  547.         var lastName = null;
  548.         for (var name in this.attrs)
  549.         {
  550.             if (lastName)
  551.                 blocks.push(',');
  552.             lastName = name;
  553.  
  554.             var val = this.attrs[name];
  555.             blocks.push('"', name, '":');
  556.             addParts(val, '', blocks, info);
  557.         }
  558.  
  559.         blocks.push('});\n');
  560.         //this.generateChildMarkup(topBlock, topOuts, blocks, info);
  561.     },
  562.  
  563.     generateDOM: function(path, blocks, args)
  564.     {
  565.         var embedName = 'e'+path.embedIndex++;
  566.  
  567.         this.generateNodePath(path, blocks);
  568.  
  569.         var valueName = 'd' + path.renderIndex++;
  570.         var argsName = 'd' + path.renderIndex++;
  571.         blocks.push(embedName + ' = __link__(node, ', valueName, ', ', argsName, ');\n');
  572.  
  573.         return embedName;
  574.     }
  575. });
  576.  
  577. // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  578.  
  579. DomplateLoop.prototype = copyObject(DomplateTag.prototype,
  580. {
  581.     merge: function(args, oldTag)
  582.     {
  583.         this.isLoop = true;
  584.         this.varName = oldTag ? oldTag.varName : args[0];
  585.         this.iter = oldTag ? oldTag.iter : parseValue(args[1]);
  586.         this.vars = [];
  587.  
  588.         this.children = oldTag ? copyArray(oldTag.children) : [];
  589.  
  590.         var offset = Math.min(args.length, 2);
  591.         parseChildren(args, offset, this.vars, this.children);
  592.  
  593.         return creator(this, DomplateLoop);
  594.     },
  595.  
  596.     getVarNames: function(names)
  597.     {
  598.         if (this.iter instanceof Parts)
  599.             names.push(this.iter.parts[0].name);
  600.  
  601.         DomplateTag.prototype.getVarNames.apply(this, [names]);
  602.     },
  603.  
  604.     generateMarkup: function(topBlock, topOuts, blocks, info)
  605.     {
  606.         this.addCode(topBlock, topOuts, blocks);
  607.  
  608.         var iterName;
  609.         if (this.iter instanceof Parts)
  610.         {
  611.             var part = this.iter.parts[0];
  612.             iterName = part.name;
  613.  
  614.             if (part.format)
  615.             {
  616.                 for (var i = 0; i < part.format.length; ++i)
  617.                     iterName = part.format[i] + "(" + iterName + ")";
  618.             }
  619.         }
  620.         else
  621.             iterName = this.iter;
  622.  
  623.         blocks.push('__loop__.apply(this, [', iterName, ', __out__, function(', this.varName, ', __out__) {\n');
  624.         this.generateChildMarkup(topBlock, topOuts, blocks, info);
  625.         this.addCode(topBlock, topOuts, blocks);
  626.         blocks.push('}]);\n');
  627.     },
  628.  
  629.     generateDOM: function(path, blocks, args)
  630.     {
  631.         var iterName = 'd'+path.renderIndex++;
  632.         var counterName = 'i'+path.loopIndex;
  633.         var loopName = 'l'+path.loopIndex++;
  634.  
  635.         if (!path.length)
  636.             path.push(-1, 0);
  637.  
  638.         var preIndex = path.renderIndex;
  639.         path.renderIndex = 0;
  640.  
  641.         var nodeCount = 0;
  642.  
  643.         var subBlocks = [];
  644.         var basePath = path[path.length-1];
  645.         for (var i = 0; i < this.children.length; ++i)
  646.         {
  647.             path[path.length-1] = basePath+'+'+loopName+'+'+nodeCount;
  648.  
  649.             var child = this.children[i];
  650.             if (isTag(child))
  651.                 nodeCount += '+' + child.tag.generateDOM(path, subBlocks, args);
  652.             else
  653.                 nodeCount += '+1';
  654.         }
  655.  
  656.         path[path.length-1] = basePath+'+'+loopName;
  657.  
  658.         blocks.push(loopName,' = __loop__.apply(this, [', iterName, ', function(', counterName,',',loopName);
  659.         for (var i = 0; i < path.renderIndex; ++i)
  660.             blocks.push(',d'+i);
  661.         blocks.push(') {\n');
  662.         blocks.push(subBlocks.join(""));
  663.         blocks.push('return ', nodeCount, ';\n');
  664.         blocks.push('}]);\n');
  665.  
  666.         path.renderIndex = preIndex;
  667.  
  668.         return loopName;
  669.     }
  670. });
  671.  
  672. // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  673.  
  674. function Variable(name, format)
  675. {
  676.     this.name = name;
  677.     this.format = format;
  678. }
  679.  
  680. function Parts(parts)
  681. {
  682.     this.parts = parts;
  683. }
  684.  
  685. // ************************************************************************************************
  686.  
  687. function parseParts(str)
  688. {
  689.     var re = /\$([_A-Za-z][_A-Za-z0-9.|]*)/g;
  690.     var index = 0;
  691.     var parts = [];
  692.  
  693.     var m;
  694.     while (m = re.exec(str))
  695.     {
  696.         var pre = str.substr(index, (re.lastIndex-m[0].length)-index);
  697.         if (pre)
  698.             parts.push(pre);
  699.  
  700.         var expr = m[1].split("|");
  701.         parts.push(new Variable(expr[0], expr.slice(1)));
  702.         index = re.lastIndex;
  703.     }
  704.  
  705.     if (!index)
  706.         return str;
  707.  
  708.     var post = str.substr(index);
  709.     if (post)
  710.         parts.push(post);
  711.  
  712.     return new Parts(parts);
  713. }
  714.  
  715. function parseValue(val)
  716. {
  717.     return typeof(val) == 'string' ? parseParts(val) : val;
  718. }
  719.  
  720. function parseChildren(args, offset, vars, children)
  721. {
  722.     for (var i = offset; i < args.length; ++i)
  723.     {
  724.         var val = parseValue(args[i]);
  725.         children.push(val);
  726.         readPartNames(val, vars);
  727.     }
  728. }
  729.  
  730. function readPartNames(val, vars)
  731. {
  732.     if (val instanceof Parts)
  733.     {
  734.         for (var i = 0; i < val.parts.length; ++i)
  735.         {
  736.             var part = val.parts[i];
  737.             if (part instanceof Variable)
  738.                 vars.push(part.name);
  739.         }
  740.     }
  741. }
  742.  
  743. function generateArg(val, path, args)
  744. {
  745.     if (val instanceof Parts)
  746.     {
  747.         var vals = [];
  748.         for (var i = 0; i < val.parts.length; ++i)
  749.         {
  750.             var part = val.parts[i];
  751.             if (part instanceof Variable)
  752.             {
  753.                 var varName = 'd'+path.renderIndex++;
  754.                 if (part.format)
  755.                 {
  756.                     for (var j = 0; j < part.format.length; ++j)
  757.                         varName = part.format[j] + '(' + varName + ')';
  758.                 }
  759.  
  760.                 vals.push(varName);
  761.             }
  762.             else
  763.                 vals.push('"'+part.replace(/"/g, '\\"')+'"');
  764.         }
  765.  
  766.         return vals.join('+');
  767.     }
  768.     else
  769.     {
  770.         args.push(val);
  771.         return 's' + path.staticIndex++;
  772.     }
  773. }
  774.  
  775. function addParts(val, delim, block, info, escapeIt)
  776. {
  777.     var vals = [];
  778.     if (val instanceof Parts)
  779.     {
  780.         for (var i = 0; i < val.parts.length; ++i)
  781.         {
  782.             var part = val.parts[i];
  783.             if (part instanceof Variable)
  784.             {
  785.                 var partName = part.name;
  786.                 if (part.format)
  787.                 {
  788.                     for (var j = 0; j < part.format.length; ++j)
  789.                         partName = part.format[j] + "(" + partName + ")";
  790.                 }
  791.  
  792.                 if (escapeIt)
  793.                     vals.push("__escape__(" + partName + ")");
  794.                 else
  795.                     vals.push(partName);
  796.             }
  797.             else
  798.                 vals.push('"'+ part + '"');
  799.         }
  800.     }
  801.     else if (isTag(val))
  802.     {
  803.         info.args.push(val);
  804.         vals.push('s'+info.argIndex++);
  805.     }
  806.     else
  807.         vals.push('"'+ val + '"');
  808.  
  809.     var parts = vals.join(delim);
  810.     if (parts)
  811.         block.push(delim, parts);
  812. }
  813.  
  814. function isTag(obj)
  815. {
  816.     return (typeof(obj) == "function" || obj instanceof Function) && !!obj.tag;
  817. }
  818.  
  819. function creator(tag, cons)
  820. {
  821.     var fn = new Function(
  822.         "var tag = arguments.callee.tag;" +
  823.         "var cons = arguments.callee.cons;" +
  824.         "var newTag = new cons();" +
  825.         "return newTag.merge(arguments, tag);");
  826.  
  827.     fn.tag = tag;
  828.     fn.cons = cons;
  829.     extend(fn, Renderer);
  830.  
  831.     return fn;
  832. }
  833.  
  834. // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  835.  
  836. function copyArray(oldArray)
  837. {
  838.     var ary = [];
  839.     if (oldArray)
  840.         for (var i = 0; i < oldArray.length; ++i)
  841.             ary.push(oldArray[i]);
  842.    return ary;
  843. }
  844.  
  845. function copyObject(l, r)
  846. {
  847.     var m = {};
  848.     extend(m, l);
  849.     extend(m, r);
  850.     return m;
  851. }
  852.  
  853. function extend(l, r)
  854. {
  855.     for (var n in r)
  856.         l[n] = r[n];
  857. }
  858.  
  859. // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  860.  
  861. function ArrayIterator(array)
  862. {
  863.     var index = -1;
  864.  
  865.     this.next = function()
  866.     {
  867.         if (++index >= array.length)
  868.             throw StopIteration;
  869.  
  870.         return array[index];
  871.     };
  872. }
  873.  
  874. function StopIteration() {}
  875.  
  876. FBL.$break = function()
  877. {
  878.     throw StopIteration;
  879. };
  880.  
  881. // ************************************************************************************************
  882.  
  883. var Renderer =
  884. {
  885.     renderHTML: function(args, outputs, self)
  886.     {
  887.         var code = [];
  888.         var markupArgs = [code, this.tag.getContext(), args, outputs];
  889.         markupArgs.push.apply(markupArgs, this.tag.markupArgs);
  890.         this.tag.renderMarkup.apply(self ? self : this.tag.subject, markupArgs);
  891.         return code.join("");
  892.     },
  893.  
  894.     insertRows: function(args, before, self)
  895.     {
  896.         if (!args)
  897.             args = {};
  898.  
  899.         this.tag.compile();
  900.  
  901.         var outputs = [];
  902.         var html = this.renderHTML(args, outputs, self);
  903.  
  904.         var doc = before.ownerDocument;
  905.         var table = doc.createElement("table");
  906.         table.innerHTML = html;
  907.  
  908.         var tbody = table.firstChild;
  909.         var parent = before.localName.toLowerCase() == "tr" ? before.parentNode : before;
  910.         var after = before.localName.toLowerCase() == "tr" ? before.nextSibling : null;
  911.  
  912.         var firstRow = tbody.firstChild, lastRow;
  913.         while (tbody.firstChild)
  914.         {
  915.             lastRow = tbody.firstChild;
  916.             if (after)
  917.                 parent.insertBefore(lastRow, after);
  918.             else
  919.                 parent.appendChild(lastRow);
  920.         }
  921.  
  922.         // To save the next poor soul:
  923.         // In order to properly apply properties and event handlers on elements
  924.         // constructed by a FOR tag, the tag needs to be able to iterate up and
  925.         // down the tree, meaning if FOR is the root element as is the case with
  926.         // many insertRows calls, it will need to iterator over portions of the
  927.         // new parent.
  928.         //
  929.         // To achieve this end, __path__ defines the -1 operator which allows
  930.         // parent traversal. When combined with the offset that we calculate
  931.         // below we are able to iterate over the elements.
  932.         //
  933.         // This fails when applied to a non-loop element as non-loop elements
  934.         // Do not generate to proper path to bounce up and down the tree.
  935.         var offset = 0;
  936.         if (this.tag.isLoop)
  937.         {
  938.             var node = firstRow.parentNode.firstChild;
  939.             for (; node && node != firstRow; node = node.nextSibling)
  940.                 ++offset;
  941.         }
  942.  
  943.         // strict warning: this.tag.context undefined
  944.         var domArgs = [firstRow, this.tag.getContext(), offset];
  945.         domArgs.push.apply(domArgs, this.tag.domArgs);
  946.         domArgs.push.apply(domArgs, outputs);
  947.  
  948.         this.tag.renderDOM.apply(self ? self : this.tag.subject, domArgs);
  949.         return [firstRow, lastRow];
  950.     },
  951.  
  952.     insertBefore: function(args, before, self)
  953.     {
  954.         return this.insertNode(
  955.                 args, before.ownerDocument,
  956.                 function(frag) {
  957.                     before.parentNode.insertBefore(frag, before);
  958.                 },
  959.                 self);
  960.     },
  961.  
  962.     insertAfter: function(args, after, self)
  963.     {
  964.         return this.insertNode(
  965.                 args, after.ownerDocument,
  966.                 function(frag) {
  967.                     after.parentNode.insertBefore(frag, after.nextSibling);
  968.                 },
  969.                 self);
  970.     },
  971.  
  972.     insertNode: function(args, doc, inserter, self)
  973.     {
  974.         if (!args)
  975.             args = {};
  976.  
  977.         this.tag.compile();
  978.  
  979.         var outputs = [];
  980.         var html = this.renderHTML(args, outputs, self);
  981.         var range = doc.createRange();
  982.         range.selectNode(doc.body);
  983.         var frag = range.createContextualFragment(html);
  984.  
  985.         var root = frag.firstChild;
  986.         root = inserter(frag) || root;
  987.  
  988.         var domArgs = [root, this.tag.context, 0];
  989.         domArgs.push.apply(domArgs, this.tag.domArgs);
  990.         domArgs.push.apply(domArgs, outputs);
  991.  
  992.         this.tag.renderDOM.apply(self ? self : this.tag.subject, domArgs);
  993.  
  994.         return root;
  995.     },
  996.  
  997.     replace: function(args, parent, self)
  998.     {
  999.         if (!args)
  1000.             args = {};
  1001.  
  1002.         this.tag.compile();
  1003.  
  1004.         var outputs = [];
  1005.         var html = this.renderHTML(args, outputs, self);
  1006.  
  1007.         var root;
  1008.         if (parent.nodeType == 1)
  1009.         {
  1010.             parent.innerHTML = html;
  1011.             root = parent.firstChild;
  1012.         }
  1013.         else
  1014.         {
  1015.             if (!parent || parent.nodeType != 9)
  1016.                 parent = document;
  1017.  
  1018.             if (!womb || womb.ownerDocument != parent)
  1019.                 womb = parent.createElement("div");
  1020.             womb.innerHTML = html;
  1021.  
  1022.             root = womb.firstChild;
  1023.             //womb.removeChild(root);
  1024.         }
  1025.  
  1026.         var domArgs = [root, this.tag.context, 0];
  1027.         domArgs.push.apply(domArgs, this.tag.domArgs);
  1028.         domArgs.push.apply(domArgs, outputs);
  1029.         this.tag.renderDOM.apply(self ? self : this.tag.subject, domArgs);
  1030.  
  1031.         return root;
  1032.     },
  1033.  
  1034.     append: function(args, parent, self)
  1035.     {
  1036.         if (!args)
  1037.             args = {};
  1038.  
  1039.         this.tag.compile();
  1040.  
  1041.         var outputs = [];
  1042.         var html = this.renderHTML(args, outputs, self);
  1043.         if (!womb || womb.ownerDocument != parent.ownerDocument)
  1044.             womb = parent.ownerDocument.createElement("div");
  1045.         womb.innerHTML = html;
  1046.  
  1047.         var root = womb.firstChild;
  1048.         while (womb.firstChild)
  1049.             parent.appendChild(womb.firstChild);
  1050.  
  1051.         var domArgs = [root, this.tag.context, 0];
  1052.         domArgs.push.apply(domArgs, this.tag.domArgs);
  1053.         domArgs.push.apply(domArgs, outputs);
  1054.  
  1055.         this.tag.renderDOM.apply(self ? self : this.tag.subject, domArgs);
  1056.  
  1057.         return root;
  1058.     }
  1059. };
  1060.  
  1061. // ************************************************************************************************
  1062.  
  1063. function defineTags()
  1064. {
  1065.     for (var i = 0; i < arguments.length; ++i)
  1066.     {
  1067.         var tagName = arguments[i];
  1068.         var fn = new Function("var newTag = new DomplateTag('"+tagName+"'); return newTag.merge(arguments);");
  1069.  
  1070.         var fnName = tagName.toUpperCase();
  1071.         FBL[fnName] = fn;
  1072.     }
  1073. }
  1074.  
  1075. defineTags(
  1076.     "a", "button", "br", "canvas", "col", "colgroup", "div", "fieldset", "form", "h1", "h2", "h3", "hr",
  1077.      "img", "input", "label", "legend", "li", "ol", "optgroup", "option", "p", "pre", "select", "b",
  1078.     "span", "strong", "table", "tbody", "td", "textarea", "tfoot", "th", "thead", "tr", "tt", "ul",
  1079.     "iframe", "code"
  1080. );
  1081.  
  1082. })();
  1083.